#include "Util.h"

#include <stdio.h>
#include <stdarg.h>
#include <string>
#include <stack>

#include <varargs.h>

#include <istream>
#include <iostream>
#include <fstream>
#include <iterator>

#include <windows.h>
#include <Shlwapi.h>

#include "External/libpng/png.h"
//#pragma comment (lib, "External/libpng/libpng.lib")

 
//using namespace std;
color4	color4::white(1,1,1,1);
color4	color4::black(0,0,0,1);
color4	color4::red(1,0,0,1);
color4	color4::green(0,1,0,1);
color4	color4::blue(0,0,1,1);


vec3	randomSphericalPosition(float minrad,float radius){
	vec3 gen;
	float len;
	do{
		gen=vec3(frand()*radius,frand()*radius,frand()*radius);
		len=length(gen);
	}while(len<minrad||len>radius);
	return gen;
}

float frrand(float	min,float max){
	return (((float)rand()/(float)RAND_MAX)*(max-min))+min;
}

float frand(){
	return ((float)rand()/(float)(RAND_MAX/2))-1.0f;
}

bool dequals(double a,double b,double tol){
	double c=b-a;
	if(c>=-tol&&c<=tol)return true;
	return false;
}
bool iszero(float a){
	return (a>-ROUNDING_ERROR_f32&&a<ROUNDING_ERROR_f32)?true:false;
}

float reciprocal_squareroot(float val){
	return 1.0f/sqrtf(val);
}

float reciprocal(float v){
	return 1.0f/v;
}

void	 setf3(float* f3, float x,float y,float z){
	f3[0]=x;
	f3[1]=y;
	f3[2]=z;
}

void	 setf2(float* f2, float x,float y){
	f2[0]=x;
	f2[1]=y;
}


static void gluMakeIdentityf(GLfloat m[16])
{
    m[0+4*0] = 1; m[0+4*1] = 0; m[0+4*2] = 0; m[0+4*3] = 0;
    m[1+4*0] = 0; m[1+4*1] = 1; m[1+4*2] = 0; m[1+4*3] = 0;
    m[2+4*0] = 0; m[2+4*1] = 0; m[2+4*2] = 1; m[2+4*3] = 0;
    m[3+4*0] = 0; m[3+4*1] = 0; m[3+4*2] = 0; m[3+4*3] = 1;
}

static int gluInvertMatrixf(const GLfloat src[16], GLfloat inverse[16])
{
    int i, j, k, swap;
    float t;
    GLfloat temp[4][4];

    for (i=0; i<4; i++) {
	for (j=0; j<4; j++) {
	    temp[i][j] = src[i*4+j];
	}
    }
	gluMakeIdentityf(inverse);

    for (i = 0; i < 4; i++) {
	/*
	** Look for largest element in column
	*/
	swap = i;
	for (j = i + 1; j < 4; j++) {
	    if (fabs(temp[j][i]) > fabs(temp[i][i])) {
		swap = j;
	    }
	}

	if (swap != i) {
	    /*
	    ** Swap rows.
	    */
	    for (k = 0; k < 4; k++) {
		t = temp[i][k];
		temp[i][k] = temp[swap][k];
		temp[swap][k] = t;

		t = inverse[i*4+k];
		inverse[i*4+k] = inverse[swap*4+k];
		inverse[swap*4+k] = t;
	    }
	}

	if (temp[i][i] == 0) {
	    /*
	    ** No non-zero pivot.  The matrix is singular, which shouldn't
	    ** happen.  This means the user gave us a bad matrix.
	    */
	    return GL_FALSE;
	}

	t = temp[i][i];
	for (k = 0; k < 4; k++) {
	    temp[i][k] /= t;
	    inverse[i*4+k] /= t;
	}
	for (j = 0; j < 4; j++) {
	    if (j != i) {
		t = temp[j][i];
		for (k = 0; k < 4; k++) {
		    temp[j][k] -= temp[i][k]*t;
		    inverse[j*4+k] -= inverse[i*4+k]*t;
		}
	    }
	}
    }
    return GL_TRUE;
}

static void gluMultMatricesf(const GLfloat a[16], const GLfloat b[16],
				GLfloat r[16])
{
    int i, j;

    for (i = 0; i < 4; i++) {
	for (j = 0; j < 4; j++) {
	    r[i*4+j] = 
		a[i*4+0]*b[0*4+j] +
		a[i*4+1]*b[1*4+j] +
		a[i*4+2]*b[2*4+j] +
		a[i*4+3]*b[3*4+j];
	}
    }
}

static void gluMultMatrixVecf(const GLfloat matrix[16], const GLfloat in[4],
		      GLfloat out[4])
{
    int i;

    for (i=0; i<4; i++) {
	out[i] = 
	    in[0] * matrix[0*4+i] +
	    in[1] * matrix[1*4+i] +
	    in[2] * matrix[2*4+i] +
	    in[3] * matrix[3*4+i];
    }
}



GLint
gluUnProject(GLfloat winx, GLfloat winy, GLfloat winz,
		const GLfloat modelMatrix[16], 
		const GLfloat projMatrix[16],
        const GLint viewport[4],
	        GLfloat *objx, GLfloat *objy, GLfloat *objz)
{
    float finalMatrix[16];
    float in[4];
    float out[4];

    gluMultMatricesf(modelMatrix, projMatrix, finalMatrix);
    if (!gluInvertMatrixf(finalMatrix, finalMatrix)) return(GL_FALSE);

    in[0]=winx;
    in[1]=winy;
    in[2]=winz;
    in[3]=1.0;

    /* Map x and y from window coordinates */
    in[0] = (in[0] - viewport[0]) / viewport[2];
    in[1] = (in[1] - viewport[1]) / viewport[3];

    /* Map to range -1 to 1 */
    in[0] = in[0] * 2 - 1;
    in[1] = in[1] * 2 - 1;
    in[2] = in[2] * 2 - 1;

    gluMultMatrixVecf(finalMatrix, in, out);
    if (out[3] == 0.0) return(GL_FALSE);
    out[0] /= out[3];
    out[1] /= out[3];
    out[2] /= out[3];
    *objx = out[0];
    *objy = out[1];
    *objz = out[2];
    return(GL_TRUE);
}


bool IntersectTriangle(Ray& ray, vec3& p1, vec3& p2, vec3& p3, float* t, UV* uv)
{
   // Calculate intersection
   vec3 e1 = p2 - p1;
   vec3 e2 = p3 - p1;
   vec3 s1 = cross(ray.d, e2);

   const float divisor = dot(s1, e1);
   if (divisor == 0.f)
      return false;

   const float invDivisor = 1.f / divisor;

   // Compute first barycentric coordinate
   const vec3 d = ray.o - p1;
   const float b1 = dot(d, s1) * invDivisor;
   if (b1 < 0.f)
      return false;

   // Compute second barycentric coordinate
   const vec3 s2 = cross(d, e1);
   const float b2 = dot(ray.d, s2) * invDivisor;
   if (b2 < 0.f)
      return false;

   const float b0 = 1.f - b1 - b2;
   if (b0 < 0.f)
      return false;

   // Compute _t_ to intersection point
   const float ray_t = dot(e2, s2) * invDivisor;
   if (ray_t < ray.mint || ray_t > ray.maxt)
      return false;

   //---- Update t
   //if (onlyNearest)
   //{
   //   if (ray_t < ray.maxt)
   //      ray.maxt = ray_t; // ALSO UPDATE THE 'hits' list
   //   else
   //      return false;
   //}

   //---- We have a hit
   *t = ray_t;
   uv->u = b1;
   uv->v = b2;

   return true;
}


std::string format_arg_list(const char *fmt, va_list args)
{
    if (!fmt) return "";
    int   result = -1, length = 256;
    char *buffer = 0;
    while (result == -1)
    {
        if (buffer) delete [] buffer;
        buffer = new char [length + 1];
        memset(buffer, 0, length + 1);
        result = _vsnprintf(buffer, length, fmt, args);
        length *= 2;
    }
    std::string s(buffer);
    delete [] buffer;
    return s;
}

std::string format(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    std::string s = format_arg_list(fmt, args);
    va_end(args);
    return s;
}



std::vector<std::string>	logCache;
FILE*						parseLog=null;
bool						logStarted=false;
bool						loggingEnabled=true;

void logging(bool	enable){
	if(loggingEnabled)
		flushLog();
	loggingEnabled=enable;
}

void flushLog(){
	if(!logCache.size())return;
	if(!logStarted){
		remove("parseLog.txt");
		logStarted=true;
	}
	if(fopen_s(&parseLog,"parseLog.txt","at")!=0)
		return;
	for(unsigned int i=0;i<logCache.size();i++){
		fprintf(parseLog,"%s",logCache[i].c_str());
	}
	fclose(parseLog);
	logCache.clear();
}

#include "Listener.h"

void logstr(std::string& str,bool flush){
	if(!loggingEnabled)return;
	Listener::sendMessage("logMessage",(void*)&str);
	logCache.push_back(str);
	if(flush)flushLog();
}

void logstr(const char* str,bool flush){
	logstr(std::string(str),flush);
}

//using namespace std;

// Recursive directory traversal using the Win32 API
bool listFiles(std::string path, std::string mask, std::vector<std::string>& files)
{
	HANDLE hFind = INVALID_HANDLE_VALUE;
	WIN32_FIND_DATA fdata;
	std::string fullpath;
	std::stack<std::string> folders;
	folders.push(path);
	files.clear();

	while (!folders.empty())
	{
		path = folders.top();
		fullpath = path + "\\" + mask;
		folders.pop();

		hFind = FindFirstFile(fullpath.c_str(), &fdata);

		if (hFind != INVALID_HANDLE_VALUE)
		{
			do
			{
				if (strcmp(fdata.cFileName, ".") != 0 &&
                    strcmp(fdata.cFileName, "..") != 0)
				{
					if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
					{
						folders.push(path + "\\" + fdata.cFileName);
					}
					else
					{
						files.push_back(path + "\\" + fdata.cFileName);
					}
				}
			}
			while (FindNextFile(hFind, &fdata) != 0);
		}

		if (GetLastError() != ERROR_NO_MORE_FILES)
		{
			FindClose(hFind);

			return false;
		}

		FindClose(hFind);
		hFind = INVALID_HANDLE_VALUE;
	}

	return true;
}

 
int copyfile(const char* from,const char* to)
{
  std::fstream f(from, std::fstream::in|std::fstream::binary);
  f << std::noskipws;
  std::istream_iterator<unsigned char> begin(f);
  std::istream_iterator<unsigned char> end;
 
  std::fstream f2(to,
    std::fstream::out|std::fstream::trunc|std::fstream::binary);
  std::ostream_iterator<char> begin2(f2);
 
  copy(begin, end, begin2);
  return 1;
}

bool	loadPNGInfo(const char*	fileName,std::string&	txt,int* pwidth,int* pheight,int* pchannels,char** pdata){
	int width,height,depth,nMipMaps,format;
	ubyte* pixels;
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
	FILE *file;

	png_byte pbSig[8];
	int iBitDepth, iColorType;
	double dGamma;
	png_color_16 *pBackground;
	png_byte **ppbRowPointers;

	if ((file = fopen(fileName, "rb")) == NULL) 
		return false;

	// Identify as PNG file
	fread(pbSig, 1, 8, file);
	if (!png_check_sig(pbSig, 8)){
		fclose(file);
		return false;
	}

	// Create the two png-info structures
	if ((png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, (png_error_ptr) NULL, (png_error_ptr) NULL)) == NULL){
		fclose(file);
		return false;
	}

	if ((info_ptr = png_create_info_struct(png_ptr)) == NULL){
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		fclose(file);
		return false;
	}

	// Initialize the png structure
	png_init_io(png_ptr, file);
	png_set_sig_bytes(png_ptr, 8);

	// Read all PNG info up to image data
	png_read_info(png_ptr, info_ptr);

	// Get width, height, bit-depth and color-type
	png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) &width, (png_uint_32 *) &height, &iBitDepth, &iColorType, NULL, NULL, NULL);
	depth = 1;
	nMipMaps = 1;

	// Expand all images to 8 bits / channel
	if (iBitDepth == 16) png_set_strip_16(png_ptr);
	if (iColorType == PNG_COLOR_TYPE_PALETTE) png_set_expand(png_ptr);
	if (iBitDepth < 8) png_set_expand(png_ptr);
	if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_expand(png_ptr);

	// Set the background color to draw transparent and alpha images over.
	if (png_get_bKGD(png_ptr, info_ptr, &pBackground))
		png_set_background(png_ptr, pBackground, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);

	// Set gamma conversion if required
	if (png_get_gAMA(png_ptr, info_ptr, &dGamma))
		png_set_gamma(png_ptr, 2.2, dGamma);


	// After the transformations have been registered update info_ptr data
	png_read_update_info(png_ptr, info_ptr);
	
	
	//png_txt
	png_textp myText=0;
	int		numText=0;
	if(png_get_text(png_ptr,info_ptr,&myText,&numText)>0){
		//We got text data from the png.
		numText=0;
		txt=myText->text;
	}


	// Get all info again
	png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) &width, (png_uint_32 *) &height, &iBitDepth, &iColorType, NULL, NULL, NULL);
	int nChannels = png_get_channels(png_ptr, info_ptr);
	switch (nChannels){
		case 1:
			format = FORMAT_I8;
			break;
		case 2:
			format = FORMAT_IA8;
			break;
		case 3:
			format = FORMAT_RGB8;
			break;
		case 4:
			format = FORMAT_RGBA8;
			break;
	}
	if(pchannels)pchannels[0]=nChannels;
	if(pwidth)pwidth[0]=width;
	if(pheight)pheight[0]=height;
	// Allocate memory
	pixels = new ubyte[width * height * nChannels];

	// Pointers for scanlines
	ppbRowPointers = new png_bytep[height];
	for (int i = 0; i < height; i++)
		ppbRowPointers[i] = pixels + i * width * nChannels;

	png_read_image(png_ptr, ppbRowPointers);
	png_read_end(png_ptr, NULL);

	delete ppbRowPointers;

	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

	fclose(file);
	if(pdata){
		*pdata=(char*)pixels;
	}else
		delete[] pixels;
	return true;
}


bool fileExists(const char *fileName)
{
    DWORD       fileAttr;
    fileAttr = GetFileAttributes(fileName);
    if (0xFFFFFFFF == fileAttr)
        return false;
    return true;
}

bool	getFileModTime(const char*	fname,FILETIME &ft){

	//SYSTEMTIME st;
	HANDLE fh;
	//wchar_t date[80], time[80];
 
	fh = CreateFile(fname, GENERIC_READ,
	    0, NULL, OPEN_EXISTING, 0, NULL);
	if (fh == INVALID_HANDLE_VALUE) {
		return false;
	}
	if (GetFileTime(fh, NULL, NULL, &ft) == 0){
		CloseHandle(fh);
		return false;
	}
	CloseHandle(fh);
	return true;
}

bool fileExistsAndIsNewerThan(const char* checkFile,const char* compareFile){
	if(fileExists(checkFile)==false)return false;
	FILETIME cmpmodtime;
	FILETIME chkmodtime;
	if(getFileModTime(checkFile,chkmodtime)&&getFileModTime(compareFile,cmpmodtime)){
		int64 t0=((int64)cmpmodtime.dwLowDateTime|((int64)cmpmodtime.dwHighDateTime<<32));
		int64 t1=((int64)chkmodtime.dwLowDateTime|((int64)chkmodtime.dwHighDateTime<<32));
		if(t1>t0){
			return true;
		}
	}
	return false;
}

void	renderString(const char*	str,vec3&	o,vec3&	u,vec3&	v);

class UtilListener	: public Listener
{
public:
	virtual bool	message(const char *msg=null,void*	params=null){
		if(msg=="unhandledMessage"){}
		else if(msg=="logMessage"){
		}else if(msg=="renderLog"){
			vec3 vo=vec3(-1.25f,0.75f,0);
			int ty=0;
			unsigned int maxLogLines=20;
			for(unsigned int t=logCache.size()>=maxLogLines?logCache.size()-maxLogLines:0;t<logCache.size();t++,ty++){				
				renderString(logCache[t].c_str(),vo+(vec3(0.0f,-0.1f,0)*(float)ty),vec3(0.01f,0,0),vec3(0,0.01f,0));
			}
		}
		return true;
	}
	UtilListener(){
		Listener::listen(this,"unhandledMessage");
		Listener::listen(this,"logMessage");
		Listener::listen(this,"renderLog");
	}
};

UtilListener	gUtilListener;
